using DotRecast.Core;
using DotRecast.Recast.Toolset.Geom;
using DotRecast.Recast;
using UnityEngine;
using DotRecast.Recast.Toolset;
using System.Collections.Generic;
using DotRecast.Detour;
using DotRecast.Recast.Toolset.Builder;
using System.Linq;
using DotRecast.Recast.Toolset.Tools;
using Net.Component;
using Net.AI;
using System;
using System.IO;

public class RecastSystem : SingleCase<RecastSystem>
{
    private DemoInputGeomProvider _geom;
    private DtNavMesh _navMesh;
    private DtNavMeshQuery _navMeshQuery;
    private RcNavMeshBuildSettings _settings;
    private IList<RecastBuilderResult> _recastResults;

    private readonly SoloNavMeshBuilder soloNavMeshBuilder = new SoloNavMeshBuilder();
    private readonly TileNavMeshBuilder tileNavMeshBuilder = new TileNavMeshBuilder();

    public LayerMask layerMask;

    public Material m_DebugRenderMeshMaterial = null;
    private DbgRenderMesh m_DbgRenderMesh = new DbgRenderMesh();

    // Start is called before the first frame update
    void Start()
    {
        Load();
    }

    public void Save()
    {
        var mesh = Merge();
        var data = new MeshData(mesh);
        using (var stream = new FileStream(Application.dataPath + "/dotnetNavData.map", FileMode.OpenOrCreate, FileAccess.ReadWrite))
        {
            stream.Seek(0, SeekOrigin.Begin);
            stream.SetLength(0);
            stream.Write(BitConverter.GetBytes(data.vertexCount));
            stream.Write(BitConverter.GetBytes(data.triangles.Length));
            foreach (var tri in data.triangles)
                stream.Write(BitConverter.GetBytes(tri));
            stream.Write(BitConverter.GetBytes(data.vertices.Length));
            foreach (var ver in data.vertices)
            {
                stream.Write(BitConverter.GetBytes(ver.x));
                stream.Write(BitConverter.GetBytes(ver.y));
                stream.Write(BitConverter.GetBytes(ver.z));
            }
            stream.Flush();
        }
    }

    public void Load()
    {
        var data = new MeshData();
        using (var stream = new FileStream(Application.dataPath + "/dotnetNavData.map", FileMode.Open, FileAccess.Read))
        {
            var bytes = new byte[1024];
            stream.Read(bytes, 0, 4);
            data.vertexCount = BitConverter.ToInt32(bytes);
            stream.Read(bytes, 0, 4);
            var trianglesCount = BitConverter.ToInt32(bytes);
            data.triangles = new int[trianglesCount];
            bytes = new byte[trianglesCount * 4];
            stream.Read(bytes, 0, bytes.Length);
            for (int i = 0; i < trianglesCount; i++)
            {
                data.triangles[i] = BitConverter.ToInt32(bytes, i * 4);
            }
            stream.Read(bytes, 0, 4);
            var verticesCount = BitConverter.ToInt32(bytes);
            data.vertices = new Net.Vector3[verticesCount];
            bytes = new byte[verticesCount * 12];
            stream.Read(bytes, 0, bytes.Length);
            for (int i = 0; i < verticesCount; i++)
            {
                var x = BitConverter.ToSingle(bytes, i * 12);
                var y = BitConverter.ToSingle(bytes, i * 12 + 4);
                var z = BitConverter.ToSingle(bytes, i * 12 + 8);
                data.vertices[i] = new Net.Vector3(x, y, z);
            }
        }
        var context = new ObjImporterContext();
        for (int i = 0; i < data.vertices.Length; i++)
        {
            context.vertexPositions.Add(data.vertices[i].x);
            context.vertexPositions.Add(data.vertices[i].y);
            context.vertexPositions.Add(data.vertices[i].z);
        }
        context.meshFaces.AddRange(data.triangles);

        _geom = new DemoInputGeomProvider(context.vertexPositions, context.meshFaces);
        _settings = new RcNavMeshBuildSettings();

        m_DbgRenderMesh.Mesh = new Mesh();
        OnNavMeshBuildBegan();
    }

    private Mesh Merge()
    {
        //获取所有子物体的网格过滤器组件
        MeshFilter[] meshFilters = FindObjectsOfType<MeshFilter>(true).Where(mf => ((1 << mf.gameObject.layer) & layerMask) > 0).ToArray();

        //创建一个新的合并后的网格
        Mesh mergedMesh = new Mesh();

        //创建一个合并用的网格数组
        CombineInstance[] combine = new CombineInstance[meshFilters.Length];

        for (int i = 0; i < meshFilters.Length; i++)
        {
            //设置合并用的网格数组
            combine[i].mesh = meshFilters[i].sharedMesh;
            combine[i].transform = meshFilters[i].transform.localToWorldMatrix;
        }

        //合并网格
        mergedMesh.CombineMeshes(combine);

        return mergedMesh;
    }

    private void OnNavMeshBuildBegan()
    {
        if (null == _geom)
        {
            return;
        }

        NavMeshBuildResult buildResult;

        var settings = _settings;
        if (settings.tiled)
        {
            buildResult = tileNavMeshBuilder.Build(_geom, settings);
        }
        else
        {
            buildResult = soloNavMeshBuilder.Build(_geom, settings);
        }

        if (!buildResult.Success)
        {
            Debug.LogError("failed to build");
            return;
        }

        _recastResults = buildResult.RecastBuilderResults;
        _navMesh = buildResult.NavMesh;
        SetQuery(_navMesh);

        DebugDrawNavMeshWithClosedList(_navMesh, _navMeshQuery, 1 | 2);
    }

    public void DebugDrawNavMeshWithClosedList(DtNavMesh mesh, DtNavMeshQuery query, int flags)
    {
        DtNavMeshQuery q = query;
        for (int i = 0; i < mesh.GetMaxTiles(); ++i)
        {
            DtMeshTile tile = mesh.GetTile(i);
            if (tile != null && tile.data != null)
            {
                DrawMeshTile(mesh, q, tile, flags);
            }
        }
    }

    private void DrawMeshTile(DtNavMesh mesh, DtNavMeshQuery query, DtMeshTile tile, int flags)
    {
        m_DbgRenderMesh.Clear();
        List<Vector3> vertices = new List<Vector3>();
        //Begin(DebugDrawPrimitives.TRIS);
        for (int i = 0; i < tile.data.header.polyCount; ++i)
        {
            DtPoly p = tile.data.polys[i];
            if (p.GetPolyType() == DtPoly.DT_POLYTYPE_OFFMESH_CONNECTION)
            {
                continue;
            }

            if (tile.data.detailMeshes != null)
            {
                DtPolyDetail pd = tile.data.detailMeshes[i];
                if (pd != null)
                {
                    for (int j = 0; j < pd.triCount; ++j)
                    {
                        int t = (pd.triBase + j) * 4;
                        for (int k = 0; k < 3; ++k)
                        {
                            int v = tile.data.detailTris[t + k];
                            if (v < p.vertCount)
                            {
                                vertices.Add(new Vector3(tile.data.verts[p.verts[v] * 3], tile.data.verts[p.verts[v] * 3 + 1] + 0.1f,
                                    tile.data.verts[p.verts[v] * 3 + 2]));
                            }
                            else
                            {
                                vertices.Add(new Vector3(tile.data.detailVerts[(pd.vertBase + v - p.vertCount) * 3],
                                    tile.data.detailVerts[(pd.vertBase + v - p.vertCount) * 3 + 1],
                                    tile.data.detailVerts[(pd.vertBase + v - p.vertCount) * 3 + 2]));
                            }
                        }
                    }
                }
            }
        }

        m_DbgRenderMesh.AddConvexPolygon(vertices, new Color(0, 1, 0, 0.5f));

        //End();
        m_DbgRenderMesh.Rebuild();
    }

    private readonly RcVec3f m_polyPickExt = RcVec3f.Of(2, 4, 2);
    private readonly DtQueryDefaultFilter m_filter = new DtQueryDefaultFilter(
            SampleAreaModifications.SAMPLE_POLYFLAGS_ALL,
            SampleAreaModifications.SAMPLE_POLYFLAGS_DISABLED,
            new float[] { 1f, 1f, 1f, 1f, 2f, 1.5f }
        );
    private long m_startRef;
    private long m_endRef;
    private readonly RcTestNavmeshToolOption _option = new RcTestNavmeshToolOption();
    private readonly RcTestNavMeshTool _tool = new RcTestNavMeshTool();
    private List<long> m_polys = new List<long>();
    private List<RcVec3f> m_smoothPath = new List<RcVec3f>(2048);
    private List<StraightPathItem> straightPath = new List<StraightPathItem>(256);

    public List<Net.Vector3> GetPath(Net.Vector3 currPosition, Net.Vector3 destination)
    {
        var paths = new List<Net.Vector3>();
        GetPath(currPosition, destination, paths);
        return paths;
    }

    public void GetPath(Net.Vector3 currPosition, Net.Vector3 destination, List<Net.Vector3> paths)
    {
        paths.Clear();
        if (null == _geom || null == _navMeshQuery)
            return;

        var m_spos = new RcVec3f(currPosition.x, currPosition.y, currPosition.z);
        var m_epos = new RcVec3f(destination.x, destination.y, destination.z);
        _navMeshQuery.FindNearestPoly(m_spos, m_polyPickExt, m_filter, out m_startRef, out var _, out var _);
        _navMeshQuery.FindNearestPoly(m_epos, m_polyPickExt, m_filter, out m_endRef, out var _, out var _);

        //var polys = new List<long>();
        //var smoothPath = new List<RcVec3f>();

        var status = _tool.FindStraightPath(_navMeshQuery, m_startRef, m_endRef, m_spos, m_epos, m_filter, _option.enableRaycast,
            ref m_polys, ref straightPath, 2);

        if (status.Succeeded())
        {
            //m_polys = polys;
            //m_smoothPath = smoothPath;
            var vertCount = straightPath.Count;
            for (int i = 0; i < vertCount; i++)
            {
                paths.Add(new Net.Vector3(straightPath[i].pos.x, straightPath[i].pos.y, straightPath[i].pos.z));
            }
        }
    }

    private void OnDrawGizmos()
    {
        if (m_DbgRenderMesh.Mesh == null)
            return;
        Gizmos.color = new Color(0f, 1f, 1f, 0.5f);
        Gizmos.DrawMesh(m_DbgRenderMesh.Mesh);
        Gizmos.color = new Color(0f, 0f, 0f, 0.3f);
        Gizmos.DrawWireMesh(m_DbgRenderMesh.Mesh);
    }

    private void SetQuery(DtNavMesh navMesh)
    {
        _navMeshQuery = navMesh != null ? new DtNavMeshQuery(navMesh) : null;
    }

    public DemoInputGeomProvider GetInputGeom()
    {
        return _geom;
    }

    public IList<RecastBuilderResult> GetRecastResults()
    {
        return _recastResults;
    }

    public DtNavMesh GetNavMesh()
    {
        return _navMesh;
    }

    public RcNavMeshBuildSettings GetSettings()
    {
        return _settings;
    }

    public DtNavMeshQuery GetNavMeshQuery()
    {
        return _navMeshQuery;
    }

    private DemoInputGeomProvider LoadInputMesh(string filename)
    {
        var bytes = Loader.ToBytes(filename);
        DemoInputGeomProvider geom = DemoObjImporter.Load(bytes);
        return geom;
    }
}
